<?php
/**
 * Created by PhpStorm.
 * User: ari
 * Date: 12/30/2014
 * Time: 10:56 AM
 */
namespace BC\Games\Holdem;

use BC\Config;
use BC\Games\Holdem\Exceptions\HoldemPlayerException;
use BC\SiteIndex;
use BC\Socket\SocketRequest;
use BC\Socket\WebSocket;
use BC\User\Service\AbstractServiceUser;
use BC\User\Session\UserSession;
use BC\Wallet\Bitcoin\BitcoinWallet;
use CPath\Build\IBuildable;
use CPath\Build\IBuildRequest;
use CPath\Data\Map\IKeyMap;
use CPath\Data\Map\IKeyMapper;
use CPath\Render\HTML\Attribute\Attributes;
use CPath\Render\HTML\Element\Form\HTMLButton;
use CPath\Render\HTML\Element\Form\HTMLForm;
use CPath\Render\HTML\Element\Form\HTMLInputField;
use CPath\Render\HTML\Element\Form\HTMLRangeInputField;
use CPath\Render\HTML\Element\Form\HTMLSelectField;
use CPath\Render\HTML\Element\HTMLElement;
use CPath\Render\HTML\Header\HeaderConfig;
use CPath\Render\HTML\Header\HTMLHeaderScript;
use CPath\Render\HTML\Header\HTMLHeaderStyleSheet;
use CPath\Render\HTML\Header\HTMLMetaTag;
use CPath\Render\HTML\HTMLContainer;
use CPath\Render\Map\MapRenderer;
use CPath\Request\CLI\CLIRequest;
use CPath\Request\Exceptions\RequestException;
use CPath\Request\Executable\IExecutable;
use CPath\Request\IRequest;
use CPath\Request\Log\ILogListener;
use CPath\Request\Session\ISessionRequest;
use CPath\Request\Validation\Exceptions\ValidationException;
use CPath\Response\Common\RedirectResponse;
use CPath\Response\IResponse;
use CPath\Response\Response;
use CPath\Response\ResponseRenderer;
use CPath\Route\IRoutable;
use CPath\Route\RouteBuilder;
use CPath\UnitTest\UnitTestRequestWrapper;
use Wrench\Exception\Exception;

if(!empty($argv) && $argv[0] === __FILE__)
	require_once '../../SiteIndex.php';

class HoldemTable implements IKeyMap, IBuildable, IRoutable, IExecutable, ILogListener
{
	const CLS_FORM = 'form-holdem';

	const CLS_STATE_SITTING = 'state-sitting';
	const CLS_STATE_STARTED = 'state-started';

	const CLS_HOLDEM_LOG    = 'holdem-log';
	const CLS_FIELDSET_TABLE = 'fieldset-holdem-table';
	const CLS_FIELDSET_PLAYERS = 'fieldset-players';
	const CLS_FIELDSET_CARDS = 'fieldset-cards';
	const CLS_FIELDSET_BOARD = 'fieldset-board';
	const CLS_FIELDSET_BURN = 'fieldset-burn';

	const CLS_FIELDSET_PLAYER = 'fieldset-player';
	const CLS_FIELD_MESSAGE = 'field-message';

	const CLS_FIELDSET_LOG    = 'fieldset-log';

	const ATTR_PLAYER_ID = 'data-player-id';

	const PARAM_TABLE_ID    = 'id';
	const PARAM_ROUND_ID    = 'round-id';
	const PARAM_BUY_IN      = 'buy-in';
	const PARAM_BLINDS      = 'blinds';
	const PARAM_COMMAND     = 'command';
	const PARAM_WALLET      = 'wallet-id' ;
	const PARAM_MESSAGE     = 'message';

	const OPTION_BUY_IN     = 'buy-in';

	const EVENT_READY         = 'ready';

	const EVENT_BLINDS        = 'blinds';
	const EVENT_BUY_IN        = 'buy-in';

	const EVENT_DELETE        = 'delete';
	const EVENT_CREATE        = 'create';

	const EVENT_SIT           = 'sit';
	const EVENT_STAND         = 'stand';
	const EVENT_DEALER        = 'dealer';
//	const EVENT_MESSAGE       = 'msg';

	const EVENT_NEW_ROUND     = 'new-round';
	const EVENT_ERROR         = 'error';

	const EVENT_WIN           = 'win';
	const EVENT_LOSE          = 'lose';
	const EVENT_WATCH         = 'watch';
//	const EVENT_LOG_UPDATE    = 'log-update';

	const PATH_FORMAT       = 'games/holdem/table/%s/';
	const TABLE_LOG         = 'table.log';
	const ROUND_LOG         = 'round.log';

	const BLIND_MAX         = 20000;
	const BUY_IN_MAX        = 2000000;
	const BUY_IN_MIN        = 200;

	const DEFAULT_DEALER_ID = 'dealer-default';

	/** @var ILogListener[] */
	private $mLogListeners = array();

	private $mID;

	private $mRound;

	/** @var HoldemPlayer[] */
	private $mPlayers = array();
	/** @var AbstractServiceUser */
	private $mDealer = null;

	private $mMinBuyIn = null;
	private $mMaxBuyIn = null;
	private $mSmallBlind = null;
	private $mBigBlind = null;
	private $mCreatorFingerprint = null;
	private $mRoundOffset = 0 ;

	private $mStarted = false;

	public function __construct(IRequest $Request, $tableID) {
		$this->mID = $tableID;

		$path = sprintf(HoldemTable::PATH_FORMAT, $tableID) . HoldemTable::TABLE_LOG;
		$path = Config::getContentPath($path);
		if(file_exists($path)) {
			$handle      = fopen($path, 'r');
			while ($line = fgets($handle))
				$this->readLine($Request, explode(' ', rtrim($line)));
			fclose($handle);
		}

		$this->mRound = new HoldemRound($Request, $this, $this->mRoundOffset);
	}

	/**
	 * @return HoldemRound
	 * @throws \Exception
	 */
	public function getRound() {
		return $this->mRound;
	}

	public function getRoundOffset() { return $this->mRoundOffset; }

	public function getBlinds() {
		if(!$this->mSmallBlind)
			throw new \InvalidArgumentException("Blinds were not set");
		return array($this->mSmallBlind, $this->mBigBlind);
	}

	protected function readLog(IRequest $Request) {
		$path        = Config::getContentPath(sprintf(self::PATH_FORMAT, $this->getTableID()) . self::TABLE_LOG);
		$handle      = fopen($path, 'r');
		while ($line = fgets($handle))
			$this->readLine($Request, explode(' ', rtrim($line)));
		fclose($handle);
	}

	public function getPlayers() {
		return $this->mPlayers;
	}

	public function hasPlayer($playerID) {
		foreach($this->mPlayers as $Player)
			if($playerID === $Player->getPlayerID())
				return true;
		return false;
	}

	public function hasStarted() {
		return $this->mStarted;
	}

	public function getPlayer($playerID) {
		foreach($this->mPlayers as $Player)
			if($playerID === $Player->getPlayerID())
				return $Player;
		throw new HoldemPlayerException("Player not found: " . $playerID);
	}

	protected function writeLine(IRequest $Request, $command, $_arg) {
		$args = func_get_args();
		array_shift($args);
		$this->readLine($Request, $args);

		$logPath     = sprintf(self::PATH_FORMAT, $this->getTableID()) . self::TABLE_LOG;
		$path        = Config::getContentPath($logPath);
		$handle      = fopen($path, 'a');
		if(!$handle && !is_dir(dirname($path))) {
			mkdir(dirname($path), 0777, true);
			$handle      = fopen($path, 'a');
			if(!$handle)
				throw new \Exception("Could not write to file: " . $path);
		}
		$l = 0;
		foreach($args as $i => $arg) {
			if($i > 0) $l+=fwrite($handle, ' ');
			$l+=fwrite($handle, $arg);
		}
		fwrite($handle, "\n");
		fclose($handle);

		if($Request instanceof SocketRequest) {
			/** @var WebSocket $App */
			$App = $Request->getSocket()->getClientApplication();
			$App->checkFile($logPath);
		}
		return $l;
	}

	public function setBlinds(IRequest $Request, $smallBlind, $bigBlind, $playerID) {
		if(
			$smallBlind !== $this->mSmallBlind
			&& $bigBlind !== $this->mBigBlind
		)
			$this->writeLine($Request, self::EVENT_BLINDS, $smallBlind, $bigBlind, $playerID);
	}

	public function setBuyInLimits(IRequest $Request, $minBuyIn, $maxBuyIn, $playerID) {
		if(
			$minBuyIn !== $this->mMinBuyIn
			&& $maxBuyIn !== $this->mMaxBuyIn
		)
			$this->writeLine($Request, self::EVENT_BUY_IN, $minBuyIn, $maxBuyIn, $playerID);
	}

	protected function readLine(IRequest $Request, $args) {
		switch ($args[0]) {
			case self::EVENT_BLINDS:
				list(, $this->mSmallBlind, $this->mBigBlind) = $args;
				break;

			case self::EVENT_BUY_IN:
				list(, $this->mMinBuyIn, $this->mMaxBuyIn) = $args;
				break;

			case self::EVENT_CREATE:
				list(, $userFingerprint) = $args;
				$this->mCreatorFingerprint = $userFingerprint;
				break;

			case self:: EVENT_SIT:
				list(, $playerID, $sessionFingerprint, $balance) = $args;
				if ($this->hasPlayer($playerID)) {
					throw new HoldemPlayerException("Player already sitting: " . $playerID); }

				$NewPlayer        = new HoldemPlayer($playerID, $sessionFingerprint, (int)$balance);
				$this->mPlayers[] = $NewPlayer;
				$NewPlayer->addLogListener($this);

				break;

			case self::EVENT_STAND:
				list(, $playerID) = $args;
				foreach ($this->mPlayers as $i => $Player) {
					if ($playerID === $Player->getPlayerID()) {
						unset($this->mPlayers[$i]);
						throw new \Exception("standing incomplete");
						break;
					}
				}
				throw new HoldemPlayerException("Player was not sitting: " . $playerID);
				break;

			case self::EVENT_DEALER:
				list(, $dealerID) = $args;
				$this->mDealer = new DealerUser($Request, $dealerID);
//				$this->mMasterPassphrase = $this->mDealer->decrypt($Request, $encryptedPassphrase);
				break;

			case self::EVENT_NEW_ROUND:
				list(, $roundOffset) = $args;
				$this->mRoundOffset = intval($roundOffset);
				$this->mStarted = true;
				break;

			case self::EVENT_WIN:
				list(, $playerID, $amount) = $args;
				$this->getPlayer($playerID)->addBalance($amount);
				// TODO: Transfer money

				break;

			case self::EVENT_LOSE:
				list(, $playerID, $amount) = $args;
				$this->getPlayer($playerID)->addBalance(-$amount);
				// TODO: Transfer money

				break;

			default:
				throw new \InvalidArgumentException("Invalid event: " . $args[0]);
		}
	}

	public function getDealer() {
		if(!$this->mDealer)
			throw new \InvalidArgumentException("Dealer was not set");
		return $this->mDealer;
	}

	public function getBuyInLimits() {
		$balances = array(0);
		foreach($this->getPlayers() as $Player)
			$balances[] = $Player->getBalance();
		$max = max($balances) * 5;
		$min = min($balances) / 2;
		if(!$max)
			return array(self::BUY_IN_MIN, self::BUY_IN_MAX);
		if($min < self::BUY_IN_MIN)
			$min = self::BUY_IN_MIN;
		if($max > self::BLIND_MAX)
			$max = self::BUY_IN_MIN;
		return array($min, $max);
	}

	public function setDealer(IRequest $Request, $dealerID) {
		$this->writeLine($Request, self::EVENT_DEALER, $dealerID);
	}

	public function sit(IRequest $Request, $playerID, $balance, $sessionFingerprint) {
		$this->writeLine($Request, self::EVENT_SIT, $playerID, $balance, $sessionFingerprint);
	}

	public function stand(IRequest $Request, $playerID) {
		$this->writeLine($Request, self::EVENT_STAND, $playerID);
	}

	/**
	 * Execute a command and return a response. Does not render
	 * @param IRequest $Request
	 * @throws RequestException
	 * @throws \BC\User\Session\Exceptions\UserSessionNotFound
	 * @throws \Exception
	 * @return IResponse the execution response
	 */
	function execute(IRequest $Request) {
		$Table = $this;
		$Round = $Table->getRound();

		$UserSession = null;
		$SessionRequest = $Request;
		if(!$SessionRequest instanceof ISessionRequest || !UserSession::hasSession($Request))
			throw new RequestException("Login required");

		$UserSession = UserSession::fromSession($Request);
		$playerID = UserSession::getSessionIDFromSession($Request);

		$isSitting = $this->hasPlayer($playerID);
		$Wallets = BitcoinWallet::search($UserSession->getUserFingerprints());


		$Form = new HTMLForm('POST', $this->getRequestPath(), self::CLS_FORM, self::CLS_FORM,
			new HTMLMetaTag(HTMLMetaTag::META_TITLE, 'Holdem Table: ' . $this->getTableID()),

			new HTMLHeaderScript(HeaderConfig::$JQueryPath),
			new HTMLHeaderScript(__DIR__ . '/assets/holdem.js'),
			new HTMLHeaderStyleSheet(__DIR__ . '/assets/holdem.css'),
			new HTMLHeaderStyleSheet(__DIR__ . '/assets/cards.css'),

//
//			new HTMLInputField(self::PARAM_TABLE_ID, $this->getTableID(), 'hidden', 'field-table-id',
//				new RequiredValidation()
//			),

			new HTMLElement('fieldset', self::CLS_FIELDSET_TABLE,
				new HTMLElement('legend', 'legend-table', 'Holdem Table: ' . $this->getTableID()),

				new HTMLElement('fieldset', self::CLS_FIELDSET_CARDS,
					new HTMLElement('legend', 'legend-table', 'Cards'),

					$DivHoldemPlayers = new HTMLElement('fieldset', self::CLS_FIELDSET_PLAYERS,
						new HTMLElement('legend', 'legend-players', 'Players - ' . $Round->getCurrentBetAmount() . ':' . $Round->getCurrentPotBalance())
					),
					"<br/>",

					$DivHoldemBoard = new HTMLElement('fieldset', self::CLS_FIELDSET_BOARD,
						new HTMLElement('legend', 'legend-board', 'Board')
					),

					$DivBurnCards = new HTMLElement('fieldset', self::CLS_FIELDSET_BURN,
						new HTMLElement('legend', 'legend-burn', 'Burn'),
						new HTMLElement('div', 'card card-unknown card-burn-1'),
						new HTMLElement('div', 'card card-unknown card-burn-2')
					)

				),

				$FieldSetAction = new HTMLElement('fieldset', 'fieldset-action',
					new HTMLElement('legend', 'legend-action', 'Play'),

					new HTMLButton(self::PARAM_COMMAND, 'Call', HoldemRound::EVENT_CALL),
					$RangeBet = new HTMLRangeInputField(HoldemRound::PARAM_AMOUNT),
					new HTMLButton(self::PARAM_COMMAND, 'Fold', HoldemRound::EVENT_FOLD),
					"<br/><br/>",
					new HTMLButton(self::PARAM_COMMAND, 'Bet', HoldemRound::EVENT_BET),
					$InputAmount = new HTMLInputField(HoldemRound::PARAM_CURRENT_BET),
					"<br/><br/>",
					new HTMLButton(self::PARAM_COMMAND, 'All-in', HoldemRound::EVENT_ALLIN),
					new HTMLButton(self::PARAM_COMMAND, 'Stand up', self::EVENT_STAND),
					"<br/><br/>",
//						new HTMLButton(self::PARAM_COMMAND, 'Raise', HoldemRound::EVENT_RAISE),

					new HTMLButton(self::PARAM_COMMAND, 'Check', HoldemRound::EVENT_CHECK),
					new HTMLButton(self::PARAM_COMMAND, "Ready", self::EVENT_READY)
				),

				new HTMLElement('fieldset', self::CLS_FIELDSET_LOG,
					new HTMLElement('legend', 'legend-log', 'Log'),

					function() use ($Table) {
						$tablePath = sprintf(HoldemTable::PATH_FORMAT, $Table->getTableID()) . HoldemTable::TABLE_LOG;
						$path = Config::getContentPath($tablePath);

						echo '<div class="', HoldemTable::CLS_HOLDEM_LOG, '">';
						readfile($path);

						$roundPath = sprintf(HoldemTable::PATH_FORMAT, $Table->getTableID()) . HoldemTable::ROUND_LOG;
						$path = Config::getContentPath($roundPath);
						$roundPathSize = 0;
						if(file_exists($roundPath)) {
							if($handle = @fopen($path, 'r')) {
								if ($Table->getRoundOffset())
									fseek($handle, $Table->getRoundOffset());
								fpassthru($handle);
								fclose($handle);
							}
							$roundPathSize = filesize($roundPath);
						}
						echo PHP_EOL;
						echo $Table::EVENT_WATCH, ' ', $tablePath, ' ', filesize($tablePath), PHP_EOL;
						echo $Table::EVENT_WATCH, ' ', $roundPath, ' ', $roundPathSize, PHP_EOL;
						echo '</div>';
					},

					new HTMLInputField(self::PARAM_MESSAGE, null, null, self::CLS_FIELD_MESSAGE),
					new HTMLButton(self::PARAM_COMMAND, 'Say', HoldemRound::EVENT_MESSAGE)
				),

				"<br/>",
				new HTMLElement('fieldset', 'fieldset-set-blinds',
					new HTMLElement('legend', 'legend-set-blinds', 'Set Blinds'),

					"Set Blinds (Small : Big)<br/>",
					$SelectBlinds = new HTMLSelectField(self::PARAM_BLINDS, 'field-' . self::PARAM_BLINDS),

					"<br/><br/>",
					new HTMLButton(self::PARAM_COMMAND, "Set Blinds", self::EVENT_BLINDS)
				),
				new HTMLElement('fieldset', 'fieldset-set-buy-in',
					new HTMLElement('legend', 'legend-set-buy-in', 'Set Buy-in limits'),

					"Set Buy-in (Min : Max)<br/>",
					$SelectBuyIn = new HTMLSelectField(self::OPTION_BUY_IN, 'field-' . self::OPTION_BUY_IN),

					"<br/><br/>",
					new HTMLButton(self::PARAM_COMMAND, "Set Buy-In limits", self::EVENT_BUY_IN)
				),

				new HTMLElement('fieldset', 'fieldset-info',
					new HTMLElement('legend', 'legend-info', 'Information'),

					new MapRenderer($this)
				),
				"<br/>",

				$FieldSetSit = new HTMLElement('fieldset', 'fieldset-sit',
					new HTMLElement('legend', 'legend-sit', 'Join table'),
					"Select Wallet to join table<br/>",
					$SelectWallet = new HTMLSelectField(self::PARAM_WALLET, 'field-' . self::PARAM_WALLET, array("Choose a wallet" => null),
						new Attributes('onchange', 'document.location.href = jQuery(this.form).attr("action") + "?' . self::PARAM_WALLET . '=" + jQuery(this).val()')
					)

//					new HTMLButton(self::PARAM_COMMAND, "Select", self::EVENT_SIT)
				)
			)
		);

		if(sizeof($Wallets) > 0) {
			foreach($Wallets as $Wallet) {
				$SelectWallet->addOption($Wallet->getGrantFingerprint(), $Wallet->getPGPInfo($Request)->getUserID());
			}
		} else {
			$SelectWallet->addOption(null, "A wallet is required to buy in");
		}

		$bc = 0;
		if($boardCards = $Round->getBoardCards()) {
			foreach($boardCards as $card) {
				$DivHoldemBoard->addContent(
					new HTMLElement('div', 'card card-board-' . $bc . ' C' . $card)
				);
				$bc++;
			}
		}
//		for(;$bc<5;$bc++)
//			$DivHoldemBoard->addContent(
//				new HTMLElement('div', 'card card-board-' . $bc . ' card-unknown')
//			);

		$DivHoldemBoard->setFlag($DivHoldemBoard::FLAG_SKIP_INDENTS);
		foreach($DivHoldemBoard->getContent() as $Content)
			if($Content instanceof HTMLElement)
				$Content->setFlag($Content::FLAG_SKIP_INDENTS | $Content::FLAG_SKIP_NEWLINE);

		foreach($Round->getPlayers() as $Player) {
			$DivHoldemPlayers->addContent(
				$DivPlayer = new HTMLElement('fieldset', self::CLS_FIELDSET_PLAYER,
					new Attributes(self::ATTR_PLAYER_ID, $Player->getPlayerID()),
					$Legend = new HTMLElement('legend', 'legend-player', $Player->getPlayerID())
				)
			);
			if($Player->getBetAmount() > 0)
				$Legend->addAll(" - <span class='bet-amount'>" . $Player->getBetAmount() . ':' . $Player->getBalance() . "</span>");

			// Slow and unnecessary. at least read the log ?!?
//			$encryptedCards = $this->mRound->getEncryptedPlayerCards($Request, $Player->getPlayerID());
//			foreach($encryptedCards as $encryptedCard) {
//				$DivPlayer->addContent(
//					new HTMLElement('div', 'card card-encrypted', base64_encode($encryptedCard))
//				);
//			}
		}


		list(, $maxBuyIn) = $this->getBuyInLimits();
		$maxBlind = $maxBuyIn / 50;
		for($i=10; $i<$maxBuyIn;) {
			$SelectBuyIn->addOption($i . ' ' . $i*10, $i . ' : ' . $i*10);
			$i*=2;
			$SelectBuyIn->addOption($i . ' ' . $i*10, $i . ' : ' . $i*10);
			$i*=2;
			$SelectBuyIn->addOption($i . ' ' . $i*10, $i . ' : ' . $i*10);
			$i=$i*5/4;
		}

		for($i=1; $i<$maxBlind;){
			$SelectBlinds->addOption($i . ' ' . $i*2, $i . ' : ' . $i*2);
			$i*=2;
			$SelectBlinds->addOption($i . ' ' . $i*2, $i . ' : ' . $i*2);
			$i*=2;
			$SelectBlinds->addOption($i . ' ' . $i*2, $i . ' : ' . $i*2);
			$i=$i*5/4;
		}


		$ChosenWallet = null;

		list($min, $max) = $this->getBuyInLimits();
		$default = $min;
		$step = $min;
		if(!empty($Request[self::PARAM_WALLET])) {
			$walletID = $Request[self::PARAM_WALLET];
			foreach($Wallets as $Wallet)
				if($Wallet->getGrantFingerprint() === $walletID)
					$ChosenWallet = $Wallet;

			if(!$ChosenWallet)
				throw new \InvalidArgumentException("Invalid wallet");

			$FieldSetSit->removeContent();
			$FieldSetSit->addAll(
				new HTMLElement('legend', 'legend-action', 'Authorize Wallet'),
				new HTMLInputField(self::PARAM_WALLET, $walletID, 'hidden', 'field-' . self::PARAM_WALLET),

				$ChosenWallet
					->setCommand($ChosenWallet::COMMAND_AUTHORIZE)
					->setTrustee($this->getDealer()->getFingerprint())
					->setAuthorizeLimits($min, $max, $default, $step)
					->setSuccessCallback(function (IRequest $Request, $command, $amount) use ($Table, $UserSession, $playerID) {
						if($command !== BitcoinWallet::COMMAND_AUTHORIZE)
							throw new \InvalidArgumentException("Wrong command: " . $command);
						$Table->writeLine($Request, HoldemTable::EVENT_SIT, $playerID, $UserSession->getGrantFingerprint(), $amount);
					})
			);

		}

		if($isSitting) {
			$Form->addClass(self::CLS_STATE_SITTING);
			$Player = $this->getPlayer($playerID);

			$amount = $Player->getBetAmount();
			$max = $Player->getBalance();
			$min = $amount;
			$step = 1; // ($max - $amount) / 10;
			$InputAmount->setInputValue($amount);
			$RangeBet->setInputValue($amount);
			$RangeBet->setMin($min);
			$RangeBet->setMax($max);
			$RangeBet->setStep($step);
		}

		if($Table->hasStarted())
			$Form->addClass(self::CLS_STATE_STARTED);

		if(!isset($Request[self::PARAM_COMMAND]))
			return $Form;

		$command = $Request[self::PARAM_COMMAND];


		switch ($command) {
			case self::EVENT_READY:
				if(!$this->mSmallBlind || !$this->mBigBlind) {
					throw new RequestException("Blinds have not been set"); }
				if ($Round->hasStarted()) {
					throw new RequestException("Round has already started"); }

				$Round = $this->getRound();
				$Round->ready($Request, $playerID);

				if($Round->waitingForReadyPlayers($reason))
					return new RedirectResponse(self::getRequestPath($this->getTableID()), "Command successful - " . $reason, 3);

				$offset = filesize($Round->getPath());
				$this->writeLine($Request, self::EVENT_NEW_ROUND, $offset);

				$Round->startNextRound($Request);
				$Response = new Response("Round started successfully");
				break;

			case self::EVENT_BLINDS:
				$msg = null;
				list($smallBlind, $bigBlind) = explode(' ', $Request[self::PARAM_BLINDS], 2);
				$this->setBlinds($Request, $smallBlind, $bigBlind, $playerID);
				$msg .= "Blinds set successfully\n";

				$Response = new Response($msg ?: "No options set");
				break;

			case self::EVENT_BUY_IN:
				$msg = null;

				list($minBuyIn, $maxBuyIn) = explode(' ', $Request[self::OPTION_BUY_IN], 2);
				$this->setBuyInLimits($Request, $minBuyIn, $maxBuyIn, $playerID);
				$msg .= "Buy-in limits set successfully\n";

				$Response = new Response($msg ?: "No options set");
				break;

			case self::EVENT_SIT:
				if(!$ChosenWallet)
					throw new ValidationException($Form, "No wallet chosen");
				$Request[BitcoinWallet::PARAM_COMMAND] = BitcoinWallet::COMMAND_AUTHORIZE;
				$Response = $ChosenWallet->execute($Request);
//				$Response = new RedirectResponse($this->getRequestPath(), $Response->getMessage(), $Response->getCode());
//				$PAmount = new HTMLInputField("Enter Buy-in amount", new RequiredValidation());
//				$amount  = $PAmount($Request);
//
//				$userFingerprints = $UserSession->getUserFingerprints();
//				$this->sit($Request, $playerID, $amount, $userFingerprints[0]);
//
//				$Response = new Response("Sit executed successfully");
				break;

			case self::EVENT_STAND:
				$this->stand($Request, $playerID);

				$Response = new Response("Stand executed successfully");
				break;

			case HoldemRound::EVENT_MESSAGE:
				$message = $Request[self::PARAM_MESSAGE];
				$this->getRound()->message($Request, $playerID, $message);
				$Response = new Response("Message received successfully");
				break;

			case HoldemRound::EVENT_BET:
			case HoldemRound::EVENT_CALL:
			case HoldemRound::EVENT_CHECK:
			case HoldemRound::EVENT_ALLIN:
			case HoldemRound::EVENT_FOLD:
				switch($command) {
					case HoldemRound::EVENT_BET:
						$amount = $Request[HoldemRound::PARAM_AMOUNT];
						$Round->betToAmount($Request, $playerID, $amount);
						break;
					case HoldemRound::EVENT_CALL:
						$Round->call($Request, $playerID);
						break;
					case HoldemRound::EVENT_CHECK:
						$Round->check($Request, $playerID);
						break;
					case HoldemRound::EVENT_ALLIN:
						$Round->allIn($Request, $playerID);
						break;
					case HoldemRound::EVENT_FOLD:
						$show = isset($Request[HoldemRound::PARAM_SHOW]) ? $Request[HoldemRound::PARAM_SHOW] && true : false;
						$Round->fold($Request, $playerID, $show);
						break;
				}
				if(!$Round->waitingForPlayerID($reason)) {
					$Round->executePendingDealerAction($Request);
					if (!$Round->hasStarted($whoWon)) {
						foreach ($whoWon as $args)
							$this->writeLine($Request, $args[0], $args[1], $args[2]);
//
////					if(!$Round->waitingForReadyPlayers($reason)) {
	//						$offset = filesize($Round->getPath());
	//						$this->writeLine($Request, self::EVENT_NEW_ROUND, $offset);
	//						$Round->startNextRound($Request);
	//						$Response = new Response(ucwords($command) . " received causing round to restart");
////
////						} else {
////							$Response = new Response(ucwords($command) . " received causing round to end");
////						}
//						break;
					}
				}
				$Response = new Response(ucwords($command) . " received successfully - " . $reason);
				break;

			default:
				if($command && $ChosenWallet) {
					$Response = $ChosenWallet->execute($Request);
					if(!$Response instanceof RedirectResponse)
						$Response = new RedirectResponse($this->getRequestPath(), $Response->getMessage());
					return $Response;
				}
				throw new RequestException("Invalid command: " . $command);
		}

		$Response = new RedirectResponse($this->getRequestPath(), $Response->getMessage(), 3);
		return new HTMLContainer(
			$Response,
			$Form
		);
	}

	public function getTableID() {
		return $this->mID;
	}

	public function getRequestPath() {
		return sprintf(self::PATH_FORMAT, $this->getTableID());
	}

	public function exists() {
		$path        = sprintf(self::PATH_FORMAT, $this->getTableID());
		return is_dir($path);
	}

	/**
	 * Map data to the key map
	 * @param IKeyMapper $Map the map inst to add data to
	 * @internal param \CPath\Request\IRequest $Request
	 * @internal param \CPath\Request\IRequest $Request
	 * @return void
	 */
	function mapKeys(IKeyMapper $Map) {
		$Map->map('id', $this->getTableID());
		$Map->map('url', $this->getRequestPath());
		$Map->map('options', array(
			'small-blind' => $this->mSmallBlind,
			'big-blind' => $this->mBigBlind,
			'min-buy-in' => $this->mMinBuyIn,
			'max-buy-in' => $this->mMaxBuyIn,
		));

	}

	/**
	 * Add a log entry
	 * @param mixed $msg The log message
	 * @param int $flags [optional] log flags
	 * @return int the number of listeners that processed the log entry
	 */
	function log($msg, $flags = 0) {
		$c = 0;
		foreach ($this->mLogListeners as $Log)
			$c += $Log->log($msg, $flags);

		return $c;
	}

	/**
	 * Add a log listener callback
	 * @param ILogListener $Listener
	 * @return void
	 * @throws \InvalidArgumentException if this log listener inst does not accept additional listeners
	 */
	function addLogListener(ILogListener $Listener) {
		if (!in_array($Listener, $this->mLogListeners))
			$this->mLogListeners[] = $Listener;
	}

	// Static

	static function create(IRequest $Request, $creatorFingerprint, $dealerID=null, $prefix=null) {
		do {
			$Table = new HoldemTable($Request, uniqid($prefix));
			$path = sprintf(HoldemTable::PATH_FORMAT, $Table->mID) . HoldemTable::TABLE_LOG;
			$path = Config::getContentPath($path);
		} while (is_dir($path));

		if(!is_dir($dir = dirname($path)))
			mkdir($dir, 0777, true);

		$Request->log("Holdem table created: " . $Table->getTableID());
		$Table->writeLine($Request, self::EVENT_CREATE, $creatorFingerprint, $path);
		$Table->writeLine($Request, self::EVENT_DEALER, $dealerID ?: self::DEFAULT_DEALER_ID);
		return $Table;
	}

	/**
	 * Route the request to this class object and return the object
	 * @param IRequest $Request the IRequest inst for this render
	 * @param array|null $Previous all previous response object that were passed from a handler, if any
	 * @param null|mixed $_arg [varargs] passed by route map
	 * @return void|bool|Object returns a response object
	 * If nothing is returned (or bool[true]), it is assumed that rendering has occurred and the request ends
	 * If false is returned, this static handler will be called again if another handler returns an object
	 * If an object is returned, it is passed along to the next handler
	 */
	static function routeRequestStatic(IRequest $Request, Array &$Previous = array(), $_arg = null) {
		return new HoldemTable($Request, $Request[self::PARAM_TABLE_ID]);
	}

	/**
	 * Handle this request and render any content
	 * @param IBuildRequest $Request the build request inst for this build session
	 * @return void
	 * @build --disable 0
	 * Note: Use doctag 'build' with '--disable 1' to have this IBuildable class skipped during a build
	 */
	static function handleBuildStatic(IBuildRequest $Request) {
		$Routes = new RouteBuilder($Request, new SiteIndex());
		$path = 'ANY /' . sprintf(self::PATH_FORMAT, ':' . self::PARAM_TABLE_ID);
		$Routes->writeRoute($path, __CLASS__);
	}

}

if(!empty($argv) && $argv[0] === __FILE__)
	HoldemRound::handleStaticUnitTest(new UnitTestRequestWrapper(new CLIRequest('GET', '')));